use regex::Regex;
use std::fs;
use std::io::BufReader;
use std::io::BufRead;
// use std::io::Read;
use std::io::Write;
use std::error::Error as BaseError;
use std::io::Error;
use std::io::ErrorKind;
// use std::iter::Iterator;
use std::path::Path;
use std::collections::HashMap;
use indicatif::ProgressBar;
use indicatif::ProgressStyle;
use threadpool::ThreadPool;
use std::sync::mpsc::Sender;
use std::sync::mpsc::Receiver;
// use std::sync::mpsc::channel;
use colored::Colorize;

type Out<T> = Result<T, Box<dyn BaseError>>;

macro_rules! OutErr {
    ($st:tt) => {
        {
            let e = Error::new(ErrorKind::Other, format!("{}", $st));
            Err(Box::new(e))
        }
    }
}

lazy_static!{
    static ref TABLES:Regex = Regex::new(r#"CREATE TABLE `(\w+)`"#).unwrap();
    static ref ITEM:Regex = Regex::new(r#"`(\w+)`"#).unwrap();
    static ref INERT:Regex = Regex::new(r#"INSERT INTO `(\w+)` VALUES (.+);"#).unwrap();
    static ref VAL:Regex = Regex::new(r#"\((.+?)\)"#).unwrap();
}

fn new_pro(len: u64) -> ProgressBar {
    let pro_bar = ProgressBar::new(len);
    let sty = ProgressStyle::default_bar()
        .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} {bytes_per_sec} ({eta}) {msg}")
        .progress_chars("#>-");
    pro_bar.set_style(sty.clone());
    pro_bar
}

pub trait BaseFile{
    fn open(&self) -> Out<fs::File>;
    fn scan_table(&self,pool:ThreadPool,send:Sender<Vec<String>>, filter_tables:&Vec<String>) -> Out<ThreadPool>;
    fn split_ekip(&self, sep:&str) -> Vec<String>;
}

impl <'a> BaseFile for &'a str{
    
    fn split_ekip(&self, sep:&str) -> Vec<String>{
        let mut array:Vec<String> = Vec::new();
        
        if self.contains("'"){
            // let mut pre = String::new();
            for (no, ele) in self.split("'").enumerate() {
                // if ele.trim().len() == 0 {continue}
                if no % 2 == 0{
                    for m in ele.split(sep){
                        if m.trim().len() > 0{
                            array.push(m.to_string());
                        }
                    }
                }else{
                    array.push(format!("'{}'",ele.to_string()));
                }
            }
        }else{
            let _ = self.split(sep).collect::<Vec<_>>().iter().map(|x| {
                array.push(x.to_string());
            }).collect::<Vec<_>>();
        }
        array
    }


    fn open(&self) -> Out<fs::File>{
        let p = Path::new(self);
        if p.exists(){
            Ok(fs::File::open(p)?)
        }else{
            OutErr!("no such file")
        }
    }
    fn scan_table(&self, pool:ThreadPool,tx:Sender<Vec<String>>,filter_tables:&Vec<String>) -> Out<ThreadPool>{
        let f = self.open()?;
        let mut map:HashMap<String,String> = HashMap::new();
        let mut data:HashMap<String,Vec<String>> = HashMap::new();
        let reader = BufReader::new(f);
        // let mut buffer = String::new();
        
        let mut found_table = false;
        let mut table_name = String::new();
        let mut items = String::new();
        let meta = fs::metadata(self)?;
        let pro = new_pro(meta.len());
        // let mut buf = [0; 65535];
        // let (tx, rx) = channel::<String>
        let mut lines = reader.lines();
        let mut last_table = String::new();
        // let mut line_num = 0;
        while let Some(line) =  lines.next(){
            // line_num+=1;
            let buffer = line?;
            pro.inc(buffer.len() as u64);
            // pro.println(format!("{}", &buffer.len()));
            if buffer.starts_with("--"){continue}
            if buffer.starts_with("LOCK"){continue}
            if buffer.starts_with("UNLOCK"){continue}
            if buffer.starts_with("DROP"){continue}
            if buffer.starts_with("/*"){continue}
            // let reader = BufReader::new(f);
            
            
            // if !buffer.contains("CREATE TABLE") && !buffer.contains("INSERT") && !buffer.contains("`") && !buffer.starts_with(")"){
            //     continue
            // }
            if buffer.starts_with("INSERT"){
                
                for cap in INERT.captures_iter(&buffer){
                    let t = cap[1].to_string();
                    let v = cap[2].to_string();
                    
                    if !map.contains_key(&t){
                        continue
                    }
                    let ts:Vec<String> = map.get(&t).unwrap().split(",").collect::<Vec<_>>().iter().map(|x|x.to_string()).collect();
                    // let mut used_vv = String::new();
                    if v.len() > 10000 && v.contains("),("){
                        if filter_tables.len() > 0 {
                            if let Some(_) = filter_tables.iter().position(|x|t.contains(x)){
                            }else{
                                pro.set_message(&format!("jump table : {}",&t.yellow()));
                                continue
                            }
                        }
                        pro.println(format!("{} : size: {}",&t.green(), format!("{}",v.len()).magenta().bold()));
                        let tx = tx.clone();
                        pro.set_message(&format!("{} deal in thread", &t));
                        pool.execute(move || {
                            
                            let mut tmp_data:Vec<String> = Vec::new();
                            for cap_v in VAL.captures_iter(&v){
                                let vv = cap_v[1].to_string();
                                let vs:Vec<String> = vv.as_str().split_ekip(",");
                                if vs.len() != ts.len() {
                                    continue
                                }
                                let mut d:String = String::from("{"); 
                                for i in 0..(ts.len()-1) {
                                    let m = if v.starts_with("'") && v.ends_with("'"){
                                        let vv = vs[i].replace('"', "'");
                                        format!(r#""{}": "{}","#,ts[i].replace("`",""), vv)
                                    }else{
                                        let vv = if vs[i].trim() == "NULL"{
                                            "\"\"".to_string()
                                        }else{
                                            vs[i].replace("'","\"").trim().to_string()
                                        };
                                        format!(r#""{}": {},"#, ts[i].replace("`",""), vv)
                                    };
                                    d.push_str(&m);
                                }
                                
                                let mut json_str = if d.ends_with(","){
                                    d[..d.len()-1].to_string()
                                }else{
                                    d
                                };
                                json_str.push_str("}");
                                // last_table = t.clone();
                                tmp_data.push(json_str);

        
                            }
                            tmp_data.push(format!("<<{}>>", &t));
                            tx.send(tmp_data).expect("channel closed");
                        });
                    }else{
                        if filter_tables.len() > 0 {
                            if let Some(_) = filter_tables.iter().position(|x|t.contains(x)){

                            }else{
                                pro.set_message(&format!("jump table : {}",&t.yellow()));
                                continue
                            }
                        }
                        for cap_v in VAL.captures_iter(&v){
                            let vv = cap_v[1].to_string();
                            let vs:Vec<String> = vv.as_str().split_ekip(",");
                            if vs.len() != ts.len() {
                                
                                // pro.println(format!("VV  {} ",vv.yellow()));
                                continue
                            }
                            let mut d:String = String::from("{"); 
                            for i in 0..(ts.len()-1) {
                                let m = if v.starts_with("'") && v.ends_with("'"){
                                    let vv = vs[i].replace('"', "'");
                            
                                    format!(r#""{}": "{}","#,ts[i].replace("`",""), vv)
                                }else{
                                    let vv = if vs[i].trim() == "NULL"{
                                        "\"\"".to_string()
                                    }else{
                                        vs[i].replace("'","\"").trim().to_string()
                                    };
                                    format!(r#""{}": {},"#, ts[i].replace("`",""), vv)
                                };
                                d.push_str(&m);
                            }
                            d.push_str("}");
                            let json_str = d.replace(",}","}");
                            
                            let mut a:Vec<String> = data.get(&t).expect("no such array in dict").to_owned();
                            if last_table == t{
                                pro.set_message(&format!("{} : {}\r", &t.green(), a.len()));
                            }else if last_table != ""{
                                
                                let last_rec = data.get(&last_table).unwrap();
                                pro.println(format!("{} : {}",&last_table.green(), &last_rec.len()));
                                
                                
                                if filter_tables.len() > 0 {
                                    if let Some(_) = filter_tables.iter().position(|x|last_table.contains(x)){
                                        let mut ll = last_rec.to_owned();
                                        ll.push(format!("<<{}>>",&last_table));
                                        tx.send(ll).expect("channel error");
                                    }else{
                                        pro.set_message(&format!("jump table : {}",&t.yellow()));
                                        // continue
                                    }
                                }else{
                                    let mut ll = last_rec.to_owned();
                                    ll.push(format!("<<{}>>",&last_table));
                                    tx.send(ll).expect("channel error");
                                }

                                
                                
                            }
                            last_table = t.clone();
                            a.push(json_str);
                            data.insert(t.clone(), a);
    
                        }
                    }
                    
                    
                }
                continue
            }
            if found_table{
                // if table_name == "course"{
                    // }
                if buffer.trim().starts_with("`"){
                    // pro.println(format!("II {} : {}", &table_name.green().bold(), &buffer.trim()));
                
                    for cap in ITEM.captures_iter(&buffer){
                        let item_name = cap[0].to_string();
                        if items.contains(&item_name){
                            continue;
                        }
                        
                        if items.len() > 0{
                            items.push_str(&format!(",{}",&item_name));
                        }else{
                            items.push_str(&format!("{}",&item_name));
                        }
                    }
                }
                
                
                if buffer.contains(";") && items.len()> 0{
                    found_table = false;
                    // if table_name == "standard_rubric"{
                    //     pro.println(format!("{}|{} | {:?}",line_num, &table_name, &items));
                    // }
                    map.insert(table_name.clone(), items);
                    data.insert(table_name, Vec::new());
                    table_name = String::new();
                    items = String::new();
                    continue;
                }

                
                

            }else{
                for cap in TABLES.captures_iter(&buffer){
                    table_name = cap[1].to_string();
                    // if table_name == "login_usage"{
                    //     pro.println(format!("CC {} : {}", &table_name.green().bold(), &buffer));
                    // }
                    found_table = true;
                }
                // if found_table{
                //     continue
                // }
            }
            
        }
        Ok(pool)
    }
}



pub fn do_in_sync_area(output_root:&str, rx:Receiver<Vec<String>>){
    let root = Path::new(output_root);
    if !root.exists() {
        let _ = fs::create_dir(&root);
    }
        
    loop {
        if let Ok(mut data) = rx.recv() {
            if let Some(name) = data.pop(){
                if name == "<<END>>"{
                    break;
                }
                let name = name.replace("<<", "").replace(">>","");
                let p = Path::new(output_root).join(&format!("{}.json",name));
                let mut f = if p.exists(){
                    fs::OpenOptions::new().append(true).open(&p).expect("open append mode error!")
                }else{
                    fs::File::create(&p).expect("create file error!")
                };
                let _ = data.iter().map(|x|{
                    let _ = write!(&mut f, "{}\n",x);
                }).collect::<Vec<_>>();
            }
        }else{
            break;
        }
    }
}